﻿Imports LSW.Extension
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Data
Imports System.Linq
Imports System.Text
Imports System.Windows.Forms

''' <summary>
''' DataSource component that exposes EntityFramework ObjectSet and provides navigation,
''' add/remove, and save/cancel changes buttons.
''' </summary>
''' <remarks>
''' This class contains a <see cref="EntityDataSource"/> and exposes one of the
''' entity sets available for binding.
''' </remarks>r
<ToolboxItem(True)>
<ToolboxBitmap(GetType(EntityBindingNavigator), "EntityBindingNavigator.png")>
<DefaultProperty("DataSource")>
<ComplexBindingProperties("DataSource", "DataMember")>
Public Class EntityBindingNavigator
    Inherits ToolStrip

#Region "Fields"

    Private m_DataSource As Object
    Private m_DataMember As String = String.Empty
    Private m_CurrencyManager As CurrencyManager
    Private m_ShowBtnNav As Boolean
    Private m_ShowBtnAdd As Boolean
    Private m_ShowBtnSave As Boolean

#End Region

#Region "Constructors"

    ''' <summary>
    ''' Initializes a new instance of a <see cref="EntityBindingNavigator"/>.
    ''' </summary>
    Public Sub New()
        MyBase.New()
        InitializeComponent()
        Dock = DockStyle.Top
        ShowNavigationButtons = TypeHelper.InlineAssignHelper(ShowAddRemoveButtons, TypeHelper.InlineAssignHelper(ShowSaveUndoRefreshButtons, True))
        UpdateUI()
    End Sub

#End Region

#Region "Object model"

    ''' <summary>
    ''' Gets or sets the data source for this navigator.
    ''' </summary>
    <DefaultValue(vbNullString)>
    <AttributeProvider(GetType(IListSource))>
    Public Property DataSource() As Object
        Get
            Return m_DataSource
        End Get
        Set(ByVal value As Object)
            m_DataSource = value
            UpdateCurrencyManager()
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the specific list in a <see cref="DataSource"/> object that the navigator should display.
    ''' </summary>
    <DefaultValue(vbNullString)>
    <Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", GetType(System.Drawing.Design.UITypeEditor))>
    Public Property DataMember() As String
        Get
            Return m_DataMember
        End Get
        Set(ByVal value As String)
            m_DataMember = value
            UpdateCurrencyManager()
        End Set
    End Property

    ''' <summary>
    ''' Sets the DataSource and DataMember properties at the same time.
    ''' </summary>
    Public Sub SetDataBinding(ByVal dataSource As Object, ByVal dataMember As String)
        m_DataSource = dataSource
        m_DataMember = dataMember
        UpdateCurrencyManager()
    End Sub

    ''' <summary>
    ''' Gets a reference to the list being managed by this navigator.
    ''' </summary>
    <Browsable(False)>
    Public ReadOnly Property List() As IBindingList
        Get
            Return If(m_CurrencyManager IsNot Nothing, TryCast(m_CurrencyManager.List, IBindingList), Nothing)
        End Get
    End Property

    ''' <summary>
    ''' Gets the item that is currently selected.
    ''' </summary>
    <Browsable(False)>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
    Public ReadOnly Property CurrentItem() As Object
        Get
            Return If(m_CurrencyManager IsNot Nothing, m_CurrencyManager.Current, Nothing)
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets the index of the item that is currently selected.
    ''' </summary>
    <Browsable(False)>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
    Public Property Position() As Integer
        Get
            Return If(m_CurrencyManager IsNot Nothing, m_CurrencyManager.Position, -1)
        End Get
        Set(ByVal value As Integer)
            If m_CurrencyManager IsNot Nothing Then
                m_CurrencyManager.Position = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets whether the control should display the navigation buttons.
    ''' </summary>
    <DefaultValue(True)>
    Public Property ShowNavigationButtons() As Boolean
        Get
            Return m_ShowBtnNav
        End Get
        Set(ByVal value As Boolean)
            m_ShowBtnNav = TypeHelper.InlineAssignHelper(m_ButtonFirst.Visible, TypeHelper.InlineAssignHelper(m_ButtonPrevious.Visible, TypeHelper.InlineAssignHelper(m_ButtonNext.Visible, TypeHelper.InlineAssignHelper(m_LabelCurrent.Visible, TypeHelper.InlineAssignHelper(m_ButtonLast.Visible, TypeHelper.InlineAssignHelper(m_SeparatorNavigation.Visible, value))))))
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets whether the control should display the add/remove item buttons.
    ''' </summary>
    <DefaultValue(True)>
    Public Property ShowAddRemoveButtons() As Boolean
        Get
            Return m_ShowBtnAdd
        End Get
        Set(ByVal value As Boolean)
            m_ShowBtnAdd = TypeHelper.InlineAssignHelper(m_ButtonAdd.Visible, TypeHelper.InlineAssignHelper(m_ButtonRemove.Visible, TypeHelper.InlineAssignHelper(m_SeparatorAddRemove.Visible, value)))
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets whether the control should display the save/undo/refresh buttons.
    ''' </summary>
    <DefaultValue(True)>
    Public Property ShowSaveUndoRefreshButtons() As Boolean
        Get
            Return m_ShowBtnSave
        End Get
        Set(ByVal value As Boolean)
            m_ShowBtnSave = TypeHelper.InlineAssignHelper(m_ButtonSave.Visible, TypeHelper.InlineAssignHelper(m_ButtonUndo.Visible, TypeHelper.InlineAssignHelper(m_ButtonRefresh.Visible, value)))
        End Set
    End Property

#End Region

#Region "Events"

    ''' <summary>
    ''' Occurs when the current item changes.
    ''' </summary>
    Public Event PositionChanged As EventHandler

    ''' <summary>
    ''' Raises the <see cref="PositionChanged"/> event.
    ''' </summary>
    ''' <param name="e"></param>
    Protected Overridable Sub OnPositionChanged(ByVal e As EventArgs)
        RaiseEvent PositionChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Occurs when the current item changes.
    ''' </summary>
    Public Event ListChanged As ListChangedEventHandler

    ''' <summary>
    ''' Raises the <see cref="ListChanged"/> event.
    ''' </summary>
    ''' <param name="e"></param>
    Protected Overridable Sub OnListChanged(ByVal e As ListChangedEventArgs)
        RaiseEvent ListChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Occurs before a new item is added to the list.
    ''' </summary>
    Public Event AddingNew As CancelEventHandler

    ''' <summary>
    ''' Raises the <see cref="AddingNew"/> event.
    ''' </summary>
    ''' <param name="e"><see cref="CancelEventArgs"/> that contains the event parameters.</param>
    Protected Overridable Sub OnAddingNew(ByVal e As CancelEventArgs)
        RaiseEvent AddingNew(Me, e)
    End Sub

    ''' <summary>
    ''' Occurs after a new item is added to the list.
    ''' </summary>
    Public Event AddedNew As EventHandler

    ''' <summary>
    ''' Raises the <see cref="AddedNew"/> event.
    ''' </summary>
    ''' <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
    Protected Overridable Sub OnAddedNew(ByVal e As EventArgs)
        RaiseEvent AddedNew(Me, e)
    End Sub

    ''' <summary>
    ''' Occurs before an item is removed from the list.
    ''' </summary>
    Public Event RemovingItem As CancelEventHandler

    ''' <summary>
    ''' Raises the <see cref="RemovingItem"/> event.
    ''' </summary>
    ''' <param name="e"><see cref="CancelEventArgs"/> that contains the event parameters.</param>
    Protected Overridable Sub OnRemovingItem(ByVal e As CancelEventArgs)
        RaiseEvent RemovingItem(Me, e)
    End Sub

    ''' <summary>
    ''' Occurs after an item is removed from the list.
    ''' </summary>
    Public Event RemovedItem As EventHandler

    ''' <summary>
    ''' Raises the <see cref="RemovedItem"/> event.
    ''' </summary>
    ''' <param name="e"><see cref="EventArgs"/> that contains the event parameters.</param>
    Protected Overridable Sub OnRemovedItem(ByVal e As EventArgs)
        RaiseEvent RemovedItem(Me, e)
    End Sub

#End Region

#Region "Overrides"

    ''' <summary>
    ''' Update the internal CurrencyManager when the BindingContext changes.
    ''' </summary>
    ''' <param name="e"></param>
    Protected Overrides Sub OnBindingContextChanged(ByVal e As EventArgs)
        MyBase.OnBindingContextChanged(e)
        UpdateCurrencyManager()
    End Sub

    ''' <summary>
    ''' Gets or sets how the control is docked in the parent container.
    ''' </summary>
    <DefaultValue(DockStyle.Top)>
    Public Overrides Property Dock() As DockStyle
        Get
            Return MyBase.Dock
        End Get
        Set(ByVal value As DockStyle)
            MyBase.Dock = value
        End Set
    End Property

#End Region

#Region "Implementation"

    Private m_ButtonFirst As ToolStripButton
    Private m_ButtonPrevious As ToolStripButton
    Private m_ButtonNext As ToolStripButton
    Private m_ButtonLast As ToolStripButton
    Private m_LabelCurrent As ToolStripLabel
    Private m_ButtonSave As ToolStripButton
    Private m_ButtonUndo As ToolStripButton
    Private m_ButtonRefresh As ToolStripButton
    Private m_ButtonAdd As ToolStripButton
    Private m_ButtonRemove As ToolStripButton
    Private m_SeparatorNavigation As ToolStripSeparator
    Private m_SeparatorAddRemove As ToolStripSeparator

    Private Sub InitializeComponent()
        m_ButtonFirst = New ToolStripButton("First Record", My.Resources.FirstRecord_small, AddressOf btnFirst_Click)
        m_ButtonPrevious = New ToolStripButton("Previous Record", My.Resources.PreviousRecord_small, AddressOf btnPrev_Click)
        m_LabelCurrent = New ToolStripLabel("0 of 0")
        m_ButtonNext = New ToolStripButton("Next Record", My.Resources.NextRecord_small, AddressOf btnNext_Click)
        m_ButtonLast = New ToolStripButton("Last Record", My.Resources.LastRecord_small, AddressOf btnLast_Click)

        For Each btn As ToolStripButton In New ToolStripButton() {m_ButtonFirst, m_ButtonPrevious, m_ButtonNext, m_ButtonLast}
            btn.DisplayStyle = ToolStripItemDisplayStyle.Image
            Items.Add(btn)
        Next
        Items.Insert(2, m_LabelCurrent)

        m_SeparatorNavigation = New ToolStripSeparator()
        Items.Add(m_SeparatorNavigation)

        m_ButtonAdd = New ToolStripButton("Add Item", My.Resources.NewItem_small, AddressOf btnAdd_Click)
        m_ButtonRemove = New ToolStripButton("Remove Item", My.Resources.Cancel2_small, AddressOf btnRemove_Click)
        For Each btn As ToolStripButton In New ToolStripButton() {m_ButtonAdd, m_ButtonRemove}
            btn.DisplayStyle = ToolStripItemDisplayStyle.Image
            Items.Add(btn)
        Next
        m_SeparatorAddRemove = New ToolStripSeparator()
        Items.Add(m_SeparatorAddRemove)

        m_ButtonSave = New ToolStripButton("Save", My.Resources.Save_small, AddressOf btnSave_Click)
        m_ButtonUndo = New ToolStripButton("Undo", My.Resources.Undo_small, AddressOf btnCancel_Click)
        m_ButtonRefresh = New ToolStripButton("Refresh", My.Resources.Refresh_small, AddressOf btnRefresh_Click)
        For Each btn As ToolStripButton In New ToolStripButton() {m_ButtonRefresh, m_ButtonUndo, m_ButtonSave}
            btn.Alignment = ToolStripItemAlignment.Right
            Items.Add(btn)
        Next

        For Each item As ToolStripItem In Items
            item.Visible = True
        Next
    End Sub

    'Update UI when postion/list changes
    Private Sub m_CurrencyManager_PositionChanged(ByVal sender As Object, ByVal e As EventArgs)
        UpdateUI()
        OnPositionChanged(e)
    End Sub

    Private Sub m_CurrencyManager_ListChanged(ByVal sender As Object, ByVal e As ListChangedEventArgs)
        If e.ListChangedType <> ListChangedType.ItemChanged Then
            UpdateUI()
        End If
        OnListChanged(e)
    End Sub

    Private Sub UpdateUI()
        If m_CurrencyManager IsNot Nothing Then
            m_LabelCurrent.Text = String.Format("{0} of {1}", m_CurrencyManager.Position + 1, m_CurrencyManager.Count)
            m_ButtonFirst.Enabled = TypeHelper.InlineAssignHelper(m_ButtonPrevious.Enabled, m_CurrencyManager.Position > 0)
            m_ButtonLast.Enabled = TypeHelper.InlineAssignHelper(m_ButtonNext.Enabled, m_CurrencyManager.Position < m_CurrencyManager.Count - 1)

            Dim bl = TryCast(m_CurrencyManager.List, IBindingList)
            m_ButtonAdd.Enabled = bl IsNot Nothing AndAlso bl.AllowNew
            m_ButtonRemove.Enabled = bl IsNot Nothing AndAlso bl.AllowRemove
        Else
            m_ButtonFirst.Enabled = TypeHelper.InlineAssignHelper(m_ButtonPrevious.Enabled, False)
            m_ButtonLast.Enabled = TypeHelper.InlineAssignHelper(m_ButtonNext.Enabled, False)
            m_ButtonAdd.Enabled = TypeHelper.InlineAssignHelper(m_ButtonRemove.Enabled, False)
        End If

        m_ButtonSave.Enabled = TypeHelper.InlineAssignHelper(m_ButtonUndo.Enabled, TypeHelper.InlineAssignHelper(m_ButtonRefresh.Enabled, EntityDataSource IsNot Nothing))
    End Sub

    'Navigation
    Private Sub btnFirst_Click(ByVal sender As Object, ByVal e As EventArgs)
        If m_CurrencyManager IsNot Nothing Then
            m_CurrencyManager.Position = 0
        End If
    End Sub

    Private Sub btnPrev_Click(ByVal sender As Object, ByVal e As EventArgs)
        If m_CurrencyManager IsNot Nothing AndAlso m_CurrencyManager.Position > 0 Then
            m_CurrencyManager.Position -= 1
        End If
    End Sub

    Private Sub btnNext_Click(ByVal sender As Object, ByVal e As EventArgs)
        If m_CurrencyManager IsNot Nothing AndAlso m_CurrencyManager.Position < m_CurrencyManager.Count - 1 Then
            m_CurrencyManager.Position += 1
        End If
    End Sub

    Private Sub btnLast_Click(ByVal sender As Object, ByVal e As EventArgs)
        If m_CurrencyManager IsNot Nothing Then
            m_CurrencyManager.Position = m_CurrencyManager.Count - 1
        End If
    End Sub

    'Add/remove records
    Private Sub btnAdd_Click(ByVal sender As Object, ByVal e As EventArgs)
        'Notify
        Dim ce = New CancelEventArgs()
        OnAddingNew(ce)

        If m_CurrencyManager IsNot Nothing AndAlso TypeOf m_CurrencyManager.List Is IBindingList AndAlso Not ce.Cancel Then
            'Add new
            Dim bl = TryCast(m_CurrencyManager.List, IBindingList)
            Dim newItem = bl.AddNew()

            'Notify
            OnAddedNew(e)

            'Make sure new item is selected
            m_CurrencyManager.Position = bl.IndexOf(newItem)
        End If

    End Sub

    Private Sub btnRemove_Click(ByVal sender As Object, ByVal e As EventArgs)
        'Notify
        Dim ce = New CancelEventArgs()
        OnRemovingItem(ce)

        If m_CurrencyManager IsNot Nothing AndAlso m_CurrencyManager.Current IsNot Nothing AndAlso TypeOf m_CurrencyManager.List Is IBindingList AndAlso Not ce.Cancel Then
            'Remove current item
            Dim bl = TryCast(m_CurrencyManager.List, IBindingList)
            bl.Remove(m_CurrencyManager.Current)

            'Notify
            OnRemovedItem(e)
        End If
    End Sub

    'Save/cancel/refresh
    Private Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs)
        If EntityDataSource IsNot Nothing Then
            EntityDataSource.SaveChanges()
        End If
    End Sub

    Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As EventArgs)
        If EntityDataSource IsNot Nothing Then
            EntityDataSource.CancelChanges()
        End If
    End Sub

    Private Sub btnRefresh_Click(ByVal sender As Object, ByVal e As EventArgs)
        If EntityDataSource IsNot Nothing Then
            EntityDataSource.Refresh()
        End If
    End Sub

    'Update the currency manager to handle a new DataSource, DataMember, or BindingContext
    Private Sub UpdateCurrencyManager()
        'Disconnect old
        If m_CurrencyManager IsNot Nothing Then
            RemoveHandler m_CurrencyManager.PositionChanged, AddressOf m_CurrencyManager_PositionChanged
            RemoveHandler m_CurrencyManager.ListChanged, AddressOf m_CurrencyManager_ListChanged
        End If

        'Get new currency manager
        m_CurrencyManager = Nothing
        If DataSource IsNot Nothing AndAlso Parent IsNot Nothing AndAlso BindingContext IsNot Nothing Then
            Try
                m_CurrencyManager = TryCast(BindingContext(DataSource, DataMember), CurrencyManager)
            Catch
            End Try
        End If

        'Connect new
        If m_CurrencyManager IsNot Nothing Then
            AddHandler m_CurrencyManager.PositionChanged, AddressOf m_CurrencyManager_PositionChanged
            AddHandler m_CurrencyManager.ListChanged, AddressOf m_CurrencyManager_ListChanged
        End If

        'Show UI
        UpdateUI()
    End Sub

    Private ReadOnly Property EntityDataSource() As EntityDataSource
        Get
            Return TryCast(m_DataSource, EntityDataSource)
        End Get
    End Property

    'Private Shared Function TypeHelper.InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
    '    target = value
    '    Return value
    'End Function

#End Region

End Class